#include "stdlib.hpp"

#include <purewell/types.hpp>
#include <purewell/semaphore.hpp>

namespace purewell
{

enum
{
	__SEM_NAMED,
	__SEM_UNNAMED
};

typedef struct sem_named_t
{
	int			type;
	sem_t*		key;
	std::string name;

	sem_named_t() : type(__SEM_NAMED), key(NULL) {}
} sem_named_t;

typedef struct sem_unnamed_t
{
	int			type;
	sem_t		key;
	sem_unnamed_t() : type(__SEM_UNNAMED) {}
} sem_unnamed_t;

inline
sem_t*
_getKey(PWSemaphore* obj)
{
	void* ptr(obj->getData());

	if ( __SEM_NAMED == *(int*)ptr )
	{
		return ((sem_named_t*)ptr)->key;
	}

	return &(((sem_unnamed_t*)ptr)->key);
}

inline
const char*
_getName(const PWSemaphore* obj)
{
	const void* ptr(obj->getData());

	if ( __SEM_NAMED == *(int*)ptr )
	{
		return ((sem_named_t*)ptr)->name.c_str();
	}

	return NULL;
}

PWSemaphore::PWSemaphore()
{
}

PWSemaphore::~PWSemaphore()
{
	if ( !m_pData ) return;

	if ( __SEM_NAMED == *(int*)m_pData )
	{
		sem_named_t* obj((sem_named_t*)m_pData);
		sem_close(obj->key);
		delete obj;
	}
	else
	{
		sem_unnamed_t* obj((sem_unnamed_t*)m_pData);
		sem_destroy(&(obj->key));
		delete obj;
	}

	m_pData = NULL;
}

void
PWSemaphore::lock(void)
{
	sem_wait(_getKey(this));
}

bool
PWSemaphore::lockTest(void)
{
	return -1 != sem_trywait(_getKey(this));
}

bool
PWSemaphore::lockTimeLimit(size_t msec)
{
	struct timespec ts;
	ts.tv_sec = msec / 1000;
	ts.tv_nsec = (msec % 1000) * 1000;
	return -1 != sem_timedwait(_getKey(this), &ts);
}

void
PWSemaphore::unlock(void)
{
	//return 0 == sem_post(_getKey(this));
	sem_post(_getKey(this));
}

bool
PWSemaphore::unlink(const char* name)
{
	return 0 == sem_unlink(name);
}

bool
PWSemaphore::unlink(void) const
{
	return 0 == sem_unlink(_getName(this));
}

bool
PWSemaphore::getValue(size_t& ret) const
{
	int res;
	if ( 0 == sem_getvalue(_getKey(const_cast<PWSemaphore*>(this)), &res) )
	{
		ret = (size_t)res;
		return true;
	}

	return false;
}

PWSemaphore*
PWSemaphore::create(size_t value)
{
	sem_unnamed_t* dat(NULL);
	PWSemaphore* cls(NULL);

	do
	{
		cls = new PWSemaphore;
		if ( !cls ) break;

		dat = new sem_unnamed_t;
		if ( !dat ) break;

		if ( -1 == sem_init(&(dat->key), 0, value) ) break;

		cls->m_pData = dat;
		return cls;
	} while (false);

	if ( dat )
	{
		delete dat;
		dat = NULL;
	}

	if ( cls )
	{
		cls->m_pData = NULL;
		delete cls;
		cls = NULL;
	}

	return NULL;
}

PWSemaphore*
PWSemaphore::create(const char* name, int flags, int omode, size_t value)
{
	sem_t* key(NULL);
	PWSemaphore* cls(NULL);
	sem_named_t* dat(NULL);

	do
	{
		if ( flags&O_CREAT ) key = sem_open(name, flags);
		else key = sem_open(name, flags, omode, value);
		if ( SEM_FAILED == key ) break;

		if ( NULL == (dat = new sem_named_t) ) break;

		if ( NULL == (cls = new PWSemaphore) ) break;

		dat->key = key;
		dat->name = name;

		cls->m_pData = dat;

		return cls;
	} while (false);

	if ( key )
	{
		sem_destroy(key);
		key = NULL;
	}

	if ( dat )
	{
		dat->key = NULL;
		delete dat;
		dat = NULL;
	}

	if ( cls )
	{
		cls->m_pData = NULL;
		delete cls;
		cls = NULL;
	}

	return NULL;
}

void
PWSemaphore::release(PWSemaphore* obj)
{
	delete obj;
}

};//namespace purewell

